﻿using System;
using System.Collections.Generic;
using System.IO;

namespace NEATlib
{
    class CharactersDataProvider
    {
        private static ImageDataCollection Data = new ImageDataCollection("t10k-images.idx3-ubyte", "t10k-labels.idx1-ubyte");
        public static DataCollection GetCollection(NeuralNetworkInfo info)
        {
            if (Data==null)
                Data = new ImageDataCollection("t10k-images.idx3-ubyte", "t10k-labels.idx1-ubyte");
         return Data;
        }
    }

    internal class ImageDataCollection : DataCollection
    {
        private static int MAGIC_OFFSET = 0;
        private static int OFFSET_SIZE = 4; //in bytes

        private static int LABEL_MAGIC = 2049;
        private static int IMAGE_MAGIC = 2051;

        private static int NUMBER_ITEMS_OFFSET = 4;
        private static int ITEMS_SIZE = 4;

        private static int NUMBER_OF_ROWS_OFFSET = 8;
        private static int ROWS_SIZE = 4;
        public static int ROWS = 28;

        private static int NUMBER_OF_COLUMNS_OFFSET = 12;
        private static int COLUMNS_SIZE = 4;
        public static int COLUMNS = 28;

        private static int IMAGE_OFFSET = 16;
        private static int IMAGE_SIZE = ROWS * COLUMNS;

        public ImageDataCollection(string images, string labels)
            : base()
        {
            var labelInputStream = File.OpenRead(labels);
            var imageInputStream = File.OpenRead(images);

            int BytesToRead = (int)labelInputStream.Length;
            int BufferBytesRead = 0;
            byte[] labelbuffer = new byte[labelInputStream.Length];

            while (BytesToRead > 0)
            {
                int n = labelInputStream.Read(labelbuffer, BufferBytesRead, BytesToRead);
                if (n == 0)
                    break;
                BufferBytesRead += n;
                BytesToRead -= n;
            }

            byte[] imagebuffer = new byte[imageInputStream.Length];
            BytesToRead = (int)imageInputStream.Length;
            BufferBytesRead = 0;

            while (BytesToRead > 0)
            {
                int n = imageInputStream.Read(imagebuffer, BufferBytesRead, BytesToRead);
                if (n == 0)
                    break;
                BufferBytesRead += n;
                BytesToRead -= n;
            }

            byte[] labelMagic = new byte[OFFSET_SIZE];
            Array.Copy(labelbuffer, labelMagic, OFFSET_SIZE);
            Array.Reverse(labelMagic, 0, OFFSET_SIZE);

            byte[] imageMagic = new byte[OFFSET_SIZE];
            Array.Copy(imagebuffer, imageMagic, OFFSET_SIZE);
            Array.Reverse(imageMagic, 0, OFFSET_SIZE);

            if (BitConverter.ToInt32(labelMagic, 0) != LABEL_MAGIC)
            {
                throw new Exception("Bad magic number in label file!");
            }

            if (BitConverter.ToInt32(imageMagic, 0) != IMAGE_MAGIC)
            {
                throw new Exception("Bad magic number in image file!");
            }

            var numberOfLabelsBytes = new byte[ITEMS_SIZE];
            var numberOfImagesBytes = new byte[ITEMS_SIZE];
            Array.Copy(labelbuffer, NUMBER_ITEMS_OFFSET, numberOfLabelsBytes, 0, ITEMS_SIZE);
            Array.Reverse(numberOfLabelsBytes, 0, ITEMS_SIZE);
            Array.Copy(imagebuffer, NUMBER_ITEMS_OFFSET, numberOfImagesBytes, 0, ITEMS_SIZE);
            Array.Reverse(numberOfImagesBytes, 0, ITEMS_SIZE);

            int numberOfLabels = BitConverter.ToInt32(numberOfLabelsBytes, 0);
            int numberOfImages = BitConverter.ToInt32(numberOfLabelsBytes, 0);

            if (numberOfImages != numberOfLabels)
            {
                throw new Exception("The number of labels and images do not match!");
            }

            var numRowsBytes = new byte[ITEMS_SIZE];
            var numColsBytes = new byte[ITEMS_SIZE];
            Array.Copy(imagebuffer, NUMBER_OF_ROWS_OFFSET, numRowsBytes, 0, ROWS_SIZE);
            Array.Reverse(numRowsBytes, 0, ROWS_SIZE);
            Array.Copy(imagebuffer, NUMBER_OF_COLUMNS_OFFSET, numColsBytes, 0, COLUMNS_SIZE);
            Array.Reverse(numColsBytes, 0, COLUMNS_SIZE);

            int numRows = BitConverter.ToInt32(numRowsBytes, 0);
            int numCols = BitConverter.ToInt32(numColsBytes, 0);

            if (numRows != ROWS && numRows != COLUMNS)
            {
                throw new Exception("Bad image. Rows and columns do not equal " + ROWS + "x" + COLUMNS);
            }

            for (int i = 0; i < numberOfLabels; i++)
            {
                int label = labelbuffer[OFFSET_SIZE + ITEMS_SIZE + i];
                byte[] imageData = new byte[IMAGE_SIZE];
                Array.Copy(imagebuffer, (i * IMAGE_SIZE) + IMAGE_OFFSET, imageData, 0, IMAGE_SIZE);

                double[] image = new double[IMAGE_SIZE];

                for (int j = 0; j < IMAGE_SIZE; j++)
                    if (imageData[j] > 0.6 * 255)
                        image[j] = 1;
                    else image[j] = 0;

                double[] output = new double[10];
                output[label] = 1;
                _dataCollection.Add(new DataItem() { DataSeries = image, ExpectedOutput = output });
            }
        }

        public ImageDataCollection(List<DataItem> list)
            : base(list)
        {
        }

        public ImageDataCollection GetPart(int number)
        {
            var newdata = new List<DataItem>();

            for (int i = 0; i < SamplesCount; i++)
                if (_dataCollection[i].ExpectedOutput[number] == 1)
                    newdata.Add(_dataCollection[i]);
            return new ImageDataCollection(newdata);
        }


    }
}
